---
title: Stepper
description: Display content divided into a steps sequence.
component: true
---

<ComponentPreview 
   name="stepper-demo"
   styleSwitch={true}
   button="copy"
   dots={false}
 />

## Installation

<Tabs defaultValue="cli">

<TabsList>
  <TabsTrigger value="cli">CLI</TabsTrigger>
  <TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">

<Steps>

<Step>Run the following command:</Step>

```bash
npx nyxb@latest add stepper
```

<Step>Update `tailwind.config.js`</Step>

Add the following animations to your `tailwind.config.ts` file:

```ts showLineNumbers title="tailwind.config.js" {6-13,16-17}
/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      keyframes: {
        "collapsible-down": {
          from: { height: "0" },
          to: { height: "var(--radix-collapsible-content-height)" },
        },
        "collapsible-up": {
          from: { height: "var(--radix-collapsible-content-height)" },
          to: { height: "0" },
        },
      },
      animation: {
        "collapsible-down": "collapsible-down 0.2s ease-out",
        "collapsible-up": "collapsible-up 0.2s ease-out",
      },
    },
  },
}
```

</Steps>

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>Copy and paste the following code into your project.</Step>

<ComponentSource name="stepper" />

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</Tabs>

## Usage

```tsx
import {
  Step,
  Stepper,
  useStepper,
  type StepItem,
} from "~/components/ui/stepper"
```

```tsx
const steps = [
  { label: "Step 1" },
  { label: "Step 2" },
  { label: "Step 3" },
] satisfies StepItem[]

export default function StepperDemo() {
  return (
    <div className="flex w-full flex-col gap-4">
      <Stepper initialStep={0} steps={steps}>
        {steps.map(({ label }, index) => {
          return (
            <Step key={label} label={label}>
              <div className="h-40 flex items-center justify-center my-2 border bg-secondary text-primary rounded-md">
                <h1 className="text-xl">Step {index + 1}</h1>
              </div>
            </Step>
          )
        })}
        <Footer />
      </Stepper>
    </div>
  )
}

const Footer = () => {
  const {
    nextStep,
    prevStep,
    resetSteps,
    isDisabledStep,
    hasCompletedAllSteps,
    isLastStep,
    isOptionalStep,
  } = useStepper()
  return (
    <>
      {hasCompletedAllSteps && (
        <div className="h-40 flex items-center justify-center my-2 border bg-secondary text-primary rounded-md">
          <h1 className="text-xl">Woohoo! All steps completed! 🎉</h1>
        </div>
      )}
      <div className="w-full flex justify-end gap-2">
        {hasCompletedAllSteps ? (
          <Button size="sm" onClick={resetSteps}>
            Reset
          </Button>
        ) : (
          <>
            <Button
              disabled={isDisabledStep}
              onClick={prevStep}
              size="sm"
              variant="secondary"
            >
              Prev
            </Button>
            <Button size="sm" onClick={nextStep}>
              {isLastStep ? "Finish" : isOptionalStep ? "Skip" : "Next"}
            </Button>
          </>
        )}
      </div>
    </>
  )
}
```

## Examples

### Default

<ComponentPreview 
   name="stepper-demo" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Orientation

We can pass the `orientation` prop to the Stepper component to set the orientation as "vertical" or "horizontal".

<ComponentPreview 
   name="stepper-orientation" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Description

We can add a description to the array of steps

<ComponentPreview 
   name="stepper-description" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Optional steps

If you want to make a step optional, you can add `optional: true` in the array of steps.

In this example, the second step is optional. You can visualize the change of the label in the `Next` button

<ComponentPreview 
   name="stepper-optional-steps" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Variants

There are 3 design variants for the Stepper component:

- `circle`: allows you to display each step in a circular shape. The label and description are positioned horizontally next to the button of each step.
- `circle-alt`: same circular design as the circle variant but the label and description are positioned vertically below the button of each step.
- `line`: this variant features a line layout with the title and description positioned below the line.

<ComponentPreview 
   name="stepper-variants" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Sizes

The Stepper component has 3 sizes: `sm`, `md`, and `lg` which can be set using the `size` prop.

<ComponentPreview 
   name="stepper-sizes" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Responsive

By using the orientation prop you are able to switch between horizontal (default) and vertical orientations. By default, when in mobile view the Steps component will switch to vertical orientation. You are also able to customize the breakpoint at which the component switches to vertical orientation by using the `mobileBreakpoint` prop.

### State

Sometimes it is useful to display visual feedback to the user depending on some asynchronous logic. In this case we can use the `state` prop to display a loading or error indicator with the values of `loading | error`.

This prop can be used globally within the Stepper component or locally in the Step component affected by this state.

<ComponentPreview 
   name="stepper-state" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Custom Icons

If you want to show custom icons instead of the default numerical indicators, you can do so by using the `icon` prop on the Step component.

<ComponentPreview 
   name="stepper-custom-icons" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

<Callout>
  To change the general check and error icons, we can use the `checkIcon` and
  `errorIcon` prop inside the Stepper component
</Callout>

### Clickable steps

If you need the step buttons to be clickable and to be able to set a custom logic for the onClick event, we can use the `onClickStep` prop in the Stepper component globally or in Step locally.

In this example we have placed a global alert when any step is clicked. Try clicking on any step to see the result.

<ComponentPreview 
   name="stepper-clickable-steps" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

<Callout>
  The `onClickStep` function has as parameters the index of the clicked step and
  the setter that allows to change to that step index. The setter is useful when
  we want to send an onClick event globally and we don't have access to the
  useStepper hook.
</Callout>

### Footer inside the step

When using the vertical orientation, we may want to have the footer buttons inside each step and not located at the end. To achieve this, we can simply move our footer below all the steps inside the Stepper component

<ComponentPreview 
   name="stepper-footer-inside" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Scroll tracking

If you would like the stepper to scroll to the active step when it is not in view you can do so using the `scrollTracking` prop on the Stepper component.

<ComponentPreview 
   name="stepper-scroll-tracking" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

<Callout>
  For scroll tracking to make sense, the content of each step should ideally
  include the buttons that allow the user to move forward or backward in the
  stepper, since the user should be able to see the content of the active step
  and not need to scroll to the end of the stepper in order to move forward or
  backward.
</Callout>

### With Forms

If you want to use the stepper with forms, you can do so by using the `useStepper` hook to control the component.

This example uses the `Form` component of nyxb and the `react-hook-form` hooks to create a form with zod for validations.

You can also use the component with server actions.

<ComponentPreview 
   name="stepper-form" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

### Custom styles

In this example we will change the style of the steps and the separator. In addition, we will also change the variables that define the size and gap of the icon for each step.

```tsx {5-13,15-17}
...
  <Stepper
    initialStep={0}
    steps={steps}
    styles={{
      "step-button-container": ny(
        "text-purple-700 rounded-none",
        "data-[current=true]:border-purple-500 data-[current=true]:bg-purple-50",
        "data-[active=true]:bg-purple-500 data-[active=true]:border-purple-500"
      ),
      "horizontal-step":
        "data-[completed=true]:[&:not(:last-child)]:after:bg-purple-500",
    }}
    variables={{
      "--step-icon-size": "60px",
      "--step-gap": "20px",
    }}
  >
  // Rest of the code
  </Stepper>
...
```

<ComponentPreview 
   name="stepper-custom-styles" 
   styleSwitch={true}
   button="copy"
   dots={false}
/>

To customize the styles of the Steps component, `Stepper` provides a list of css classes for each part of the component. You can use these classes to override the default styles. Below is a list of the classes that are available.

- `main-container`: The main container of the stepper.
- `horizontal-step`: Outer wrapper for each step in horizontal layout
- `horizontal-step-container`: Inner wrapper for each step in horizontal layout
- `vertical-step`: Outer wrapper for each step in vertical layout
- `vertical-step-container`: Inner wrapper for each step in vertical layout
- `vertical-step-content`: Content wrapper for each step in vertical layout
- `step-button-container`: Wrapper for the step button
- `step-label-container`: Wrapper for the label and description
- `step-label`: The label of the step
- `step-description`: The description of the step

In some cases you may want to customize the styles of a step based on its state. For example, you may want to change the color of a step when it is active. To do this, you can use the data attributes defined below.

- `data-active`: The active step
- `data-invalid`: The step with an error
- `data-loading`: The step in loading state
- `data-clickable`: The step that is clickable
- `data-completed`: The step that is completed
- `data-optional`: The step that is optional

Finally, we also have the `variables` prop that allows you to set values for the css variables that calculate the position of the separator lines. These variables can be useful when we need to set custom elements that have a different size than those offered by the component.

- `--step-icon-size`: defines the width of the step icon. It is important to define this value in pixels. By default it has the values of the width of a nyxb/ui button depending if the style is default (`36px, 40px, 44px`) or miami (`32px, 36px, 40px`).
- `--step-gap`: defines the gap between the separator and the elements that follow it. The default value is `8px`.

## API

### Stepper

<PropsTable
  data={[
    {
      name: "initialStep",
      required: true,
      type: "number",
      description: "Currently active step",
    },
    {
      name: "steps",
      required: true,
      type: "StepItem[]",
      description:
        "An array of objects with the label and description of each step.",
    },
    {
      name: "orientation",
      type: '"horizontal" | "vertical"',
      default: "horizontal",
      description: "The orientation of the stepper.",
    },
    {
      name: "size",
      type: '"sm" | "md" | "lg"',
      default: "md",
      description: "The size of the stepper.",
    },
    {
      name: "state",
      type: '"loading" | "error"',
      description: "The state of the stepper (global).",
    },
    {
      name: "icon",
      type: "LucideIcon | React.ComponentType<any>",
      description: "The custom icon to be displayed in the step button.",
    },
    {
      name: "checkIcon",
      type: "LucideIcon | React.ComponentType<any>",
      description:
        "The custom icon to be displayed when the step is completed.",
    },
    {
      name: "errorIcon",
      type: "LucideIcon | React.ComponentType<any>",
      description:
        "The custom icon to be displayed when the step has an error.",
    },
    {
      name: "responsive",
      type: "boolean",
      default: "true",
      description: "If the stepper should be responsive.",
    },
    {
      name: "mobileBreakpoint",
      type: "number",
      default: "768px",
      description:
        "The breakpoint at which the stepper switches to vertical orientation.",
    },
    {
      name: "scrollTracking",
      type: "boolean",
      default: "false",
      description:
        "Scroll to the active step when scrolling forward or backward.",
    },
    {
      name: "styles",
      type: "{ [key: string]: string }",
      description: "Custom styles for the stepper.",
    },
    {
      name: "variables",
      type: "{ [key: string]: string }",
      description: "Custom css variables values for the stepper.",
    },
    {
      name: "onClickStep",
      type: "(index: number, setStep: (index: number) => void) => void",
      description: "The function to be executed when a step is clicked.",
    },
    {
      name: "variant",
      type: '"circle" | "circle-alt" | "line"',
      default: "circle",
      description: "The design variant of the stepper.",
    },
    {
      name: "expandVerticalSteps",
      type: "boolean",
      default: "false",
      description: "Control whether or not the vertical steps should collapse.",
    },
  ]}
/>

### Step

<PropsTable
  data={[
    {
      name: "id",
      type: "string",
      description: "The id of the step.",
    },
    {
      name: "label",
      type: "string",
      description: "The label of the step.",
    },
    {
      name: "description",
      type: "string",
      description: "The description of the step.",
    },
    {
      name: "optional",
      type: "boolean",
      description: "If the step is optional.",
    },
    {
      name: "icon",
      type: "LucideIcon | React.ComponentType<any>",
      description: "The custom icon to be displayed in the step button.",
    },
    {
      name: "state",
      type: '"loading" | "error"',
      description: "The state of the step (local).",
    },
    {
      name: "isCompletedStep",
      type: "boolean",
      description:
        "Individually control each step state, defaults to active step.",
    },
    {
      name: "isKeepError",
      type: "boolean",
      description:
        "Individually control if each step should keep showing the error state.",
    },
    {
      name: "checkIcon",
      type: "LucideIcon | React.ComponentType<any>",
      description:
        "The custom icon to be displayed when the step is completed.",
    },
    {
      name: "errorIcon",
      type: "LucideIcon | React.ComponentType<any>",
      description:
        "The custom icon to be displayed when the step has an error.",
    },
    {
      name: "onClickStep",
      type: "(index: number, setStep: (index: number) => void) => void",
      description: "The function to be executed when a step is clicked.",
    },
  ]}
/>

### useStepper

In order to use the hook, we simply have to import it and use it inside the `<Stepper />` component as a wrapper.

```tsx
import { useStepper } from "~/components/ui/stepper"

export funcion UseStepperDemo() {
  { ... } = useStepper();

  return (
    <div>
      { ... }
    </div>
  )
}
```

The values returned by the hook are the following:

<PropsTable
  data={[
    {
      name: "activeStep",
      type: "number",
      description: "The current active step.",
    },
    {
      name: "isLastStep",
      type: "boolean",
      description: "If the current step is the last step.",
    },
    {
      name: "isOptionalStep",
      type: "boolean",
      description: "If the current step is optional.",
    },
    {
      name: "isDisabledStep",
      type: "boolean",
      description: "If the current step is disabled.",
    },
    {
      name: "isError",
      type: "boolean",
      description: "If the stepper has an error.",
    },
    {
      name: "isLoading",
      type: "boolean",
      description: "If the stepper is loading.",
    },
    {
      name: "isVertical",
      type: "boolean",
      description: "If the stepper is vertical.",
    },
    {
      name: "expandVerticalSteps",
      type: "boolean",
      description: "If the vertical steps should be expanded.",
    },
    {
      name: "nextStep",
      type: "() => void",
      description: "Function to go to the next step.",
    },
    {
      name: "prevStep",
      type: "() => void",
      description: "Function to go to the previous step.",
    },
    {
      name: "setStep",
      type: "(index: number) => void",
      description: "Function to set a specific step.",
    },
    {
      name: "resetSteps",
      type: "() => void",
      description: "Function to reset the stepper.",
    },
    {
      name: "stepCount",
      type: "number",
      description: "The total number of steps.",
    },
    {
      name: "initialStep",
      type: "number",
      description: "The initial active step.",
    },
    {
      name: "clickable",
      type: "boolean",
      description: "If the steps are clickable.",
    },
    {
      name: "hasCompletedAllSteps",
      type: "boolean",
      description: "If all steps have been completed.",
    },
    {
      name: "currentStep",
      type: "StepItem",
      description: "The current step object.",
    },
    {
      name: "previousActiveStep",
      type: "number",
      description:
        "The previous active step. It allows us to know which was the previous active step",
    },
  ]}
/>
